/************************************************************
* @file    	i2c_gpio.c
* @author 	dralee
* @version	v1.0
* @date			2021-12-06
* @brief		用GPIO模拟I2C总线，适用于SMT32系列CPU。
*		该模块不包括应用命令帧，仅包括I2C总线基本操作函数
*	在访问I2C设备前，请先调用I2C_CheckDevice()检测I2C设备是否
* 正常，该函数会配置GPIO
*************************************************************/

#include "i2c_gpio.h"
#include "stm32f10x.h"

static void I2C_CfgGPIO(void); // GPIO口配置


/**
 *********************************************************************************
 * @brief I2C总线位延迟，最快400KHz
 * 时间是通过逻辑分析仪器测试得到的，要作条件：CPU主频72MHz，MDK编译环境，1级优化
 * 循环次数为10时，SCL频率 = 205KHz
 * 循环次数为7时，SCL频率 = 347KHz，SCL高电平时间为1.5us，SCL低电平时间2.87us
 * 循环次数为5时，SCL频率 = 421KHz，SCL高电平时间为1.25us，SCL低电平时间2.375us
 * @param 无
 * @retval 无
 *********************************************************************************/
static void I2C_Delay(void)
{
	uint8_t i;
	for(i = 0; i < 10; i++);
}

/**
 *********************************************************************************
 * @brief CPU发起I2C总线启动信号
 * @param 无
 * @retval 无
 *********************************************************************************/
void I2C_Start(void)
{
	// 当SCL高电平时，SDA出现一个下跳沿表示I2C总线启动信号
	EEPROM_I2C_SDA_1();
	EEPROM_I2C_SCL_1();
	I2C_Delay();
	EEPROM_I2C_SDA_0();
	I2C_Delay();
	EEPROM_I2C_SCL_0();
	I2C_Delay();
}

/**
 *********************************************************************************
 * @brief CPU发起I2C总线停止信号
 * @param 无
 * @retval 无
 *********************************************************************************/
void I2C_Stop(void)
{
	// 当SCL高电平时，SDA出现一个上跳沿表示I2C总线停止信号
	EEPROM_I2C_SDA_0();
	EEPROM_I2C_SCL_1();
	I2C_Delay();
	EEPROM_I2C_SDA_1();
}

/**
 *********************************************************************************
 * @brief CPU向I2C总线设备发送8bit数据
 * @param ucByte 等待发送的字节
 * @retval 无
 *********************************************************************************/
void I2C_SendByte(uint8_t ucByte)
{
	uint8_t i;
	// 先发送字节高位bit7
	for(i = 0; i < 8; ++i)
	{
		if(ucByte & 0x80)
		{
			EEPROM_I2C_SDA_1();
		}
		else
		{
			EEPROM_I2C_SDA_0();
		}
		I2C_Delay();
		EEPROM_I2C_SCL_1();
		I2C_Delay();
		EEPROM_I2C_SCL_0();
		if(i == 7)
		{
			EEPROM_I2C_SDA_1(); // 释放总线
		}
		ucByte <<= 1; // 左移1位
		I2C_Delay();
	}
}

/**
 *********************************************************************************
 * @brief CPU从I2C总线设备读取8bit数据
 * @param 无
 * @retval 读取的数据
 *********************************************************************************/
uint8_t I2C_ReadByte(void)
{
	uint8_t i,value;
	
	// 读到第1个bit为数据的bit7
	value = 0;
	for(i = 0; i < 8; i++)
	{
		value <<= 1;
		EEPROM_I2C_SCL_1();
		I2C_Delay();
		if(EEPROM_I2C_SDA_READ())
		{
			value++;
		}
		EEPROM_I2C_SCL_0();
		I2C_Delay();
	}
	return value;
}

/**
 *********************************************************************************
 * @brief CPU产生一个时钟，并读取器件ACK应答信号
 * @param 无
* @retval 0表示正确应答，1表示无器件响应
 *********************************************************************************/
uint8_t I2C_WaitAck(void)
{
	uint8_t re;
	
	EEPROM_I2C_SDA_1();				// CPU释放SDA总线
	I2C_Delay();
	EEPROM_I2C_SCL_1();				// CPU驱动SCL=1，此时器件会返回ACK应答
	I2C_Delay();
	if(EEPROM_I2C_SDA_READ())	// CPU读取SDA口线状态
	{
		re = 1;
	}
	else
	{
		re = 0;
	}
	EEPROM_I2C_SCL_0();
	I2C_Delay();
	return re;
}

/**
 *********************************************************************************
 * @brief CPU产生一个ACK信号
 * @param 无
 * @retval 无
 *********************************************************************************/
void I2C_Ack(void)
{
	EEPROM_I2C_SDA_0();		// CPU驱动SDA = 0
	I2C_Delay();
	EEPROM_I2C_SCL_1();		// CPU产生1个时钟
	I2C_Delay();
	EEPROM_I2C_SCL_0();
	I2C_Delay();
	EEPROM_I2C_SDA_1();		// CPU释放SDA总线
}

/**
 *********************************************************************************
 * @brief CPU产生1个NACK信号
 * @param 无
 * @retval 无
 *********************************************************************************/
void I2C_NAck(void)
{
	EEPROM_I2C_SDA_1();		// CPU驱动SDA=1
	I2C_Delay();
	EEPROM_I2C_SCL_1();		// 1个时钟
	I2C_Delay();
	EEPROM_I2C_SCL_0();
	I2C_Delay();
}

/**
 *********************************************************************************
 * @brief 配置I2C总线GPIO，采用模拟IO方式实现
 * @param 无
 * @retval 无
 *********************************************************************************/
void I2C_CfgGPIO(void)
{
	GPIO_InitTypeDef	GPIO_InitStruct;
	RCC_APB2PeriphClockCmd(EEPROM_RCC_I2C_PORT, ENABLE);	// GPIO时钟
	
	GPIO_InitStruct.GPIO_Pin = EEPROM_I2C_SCL_PIN | EEPROM_I2C_SDA_PIN;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;					// 开漏输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(EEPROM_GPIO_PORT_I2C, &GPIO_InitStruct);
	
	I2C_Stop(); // 给一个停止信号，复位I2C总线上所有设备到待机模式
}

/**
 *********************************************************************************
 * @brief 检测I2C总线设备，CPU向设备发送地址，然后读取设备应答判断该设备是否存在
 * @param address 设备的I2C总线地址
 * @retval 0表示正确，1表示未探测到
 *********************************************************************************/
uint8_t I2C_CheckDevice(uint8_t address)
{
	uint8_t ucAck;
	I2C_CfgGPIO();	// 配置GPIO
	I2C_Start();		// 发送启动信号
	I2C_SendByte(address | EEPROM_I2C_WR); // 发送设备地址+读写控制位(0=w,1=r) bit7先传
	ucAck = I2C_WaitAck();	// 检测设备的ACK应答
	
	I2C_Stop();			// 发送停止信号
	
	return ucAck;
}
